#region

using System;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;

#endregion

namespace MediaController.Extractor
{
    public class Unrar : IDisposable
    {
        #region OpenMode enum

        public enum OpenMode
        {
            List,
            Extract
        }

        #endregion

        #region Data Members

        private readonly UNRARCallback _callback;
        private readonly bool _retrieveComment;

        private IntPtr _archiveHandle;
        private string _archivePathName;
        private string _comment;
        private RARFileInfo _currentFile;
        private DriveInfo _destinationInfo;
        private string _destinationPath;
        private RARHeaderDataEx _header;
        private string _password;

        #endregion

        public Unrar()
        {
            _archivePathName = string.Empty;
            _archiveHandle = new IntPtr(0);
            _retrieveComment = true;
            _password = string.Empty;
            _comment = string.Empty;
            _header = new RARHeaderDataEx();
            _destinationPath = string.Empty;
            _callback = new UNRARCallback(RARCallback);
        }

        public Unrar(string archivePathName)
            : this()
        {
            _archivePathName = archivePathName;
        }

        public string ArchivePathName
        {
            get { return _archivePathName; }
            set { _archivePathName = value; }
        }

        public string Comment
        {
            get { return _comment; }
        }

        public RARFileInfo CurrentFile
        {
            get { return _currentFile; }
        }

        public string DestinationPath
        {
            get { return _destinationPath; }
            set
            {
                if ((value != null) && !value.Equals(_destinationPath))
                {
                    _destinationPath = value;
                }
            }
        }

        public string Password
        {
            get { return _password; }
            set
            {
                _password = value;
                if (_archiveHandle != IntPtr.Zero)
                {
                    RARSetPassword(_archiveHandle, value);
                }
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (_archiveHandle != IntPtr.Zero)
            {
                RARCloseArchive(_archiveHandle);
                _archiveHandle = IntPtr.Zero;
            }
        }

        #endregion

        [DllImport("unrar.dll")]
        private static extern int RARCloseArchive(IntPtr hArcData);

        [DllImport("unrar.dll")]
        private static extern IntPtr RAROpenArchive(ref RAROpenArchiveData archiveData);

        [DllImport("UNRAR.DLL")]
        private static extern IntPtr RAROpenArchiveEx(ref RAROpenArchiveDataEx archiveData);

        [DllImport("unrar.dll")]
        private static extern int RARProcessFile(IntPtr hArcData, int operation,
                                                 [MarshalAs(UnmanagedType.LPStr)] string destPath,
                                                 [MarshalAs(UnmanagedType.LPStr)] string destName);

        [DllImport("unrar.dll")]
        private static extern int RARReadHeader(IntPtr hArcData, ref RARHeaderData headerData);

        [DllImport("unrar.dll")]
        private static extern int RARReadHeaderEx(IntPtr hArcData, ref RARHeaderDataEx headerData);

        [DllImport("unrar.dll")]
        private static extern void RARSetCallback(IntPtr hArcData, UNRARCallback callback, int userData);

        [DllImport("unrar.dll")]
        private static extern void RARSetPassword(IntPtr hArcData, [MarshalAs(UnmanagedType.LPStr)] string password);

        public event DataAvailableHandler DataAvailable;

        public event ExtractionProgressHandler ExtractionProgress;

        public event MissingVolumeHandler MissingVolume;

        public event NewFileHandler NewFile;

        public event NewVolumeHandler NewVolume;

        public event OutOfDiskSpaceHandler OutOfDiskSpace;

        public event PasswordRequiredHandler PasswordRequired;

        public void Open()
        {
            if (ArchivePathName.Length == 0)
            {
                throw new IOException("Archive name has not been set.");
            }
            Open(ArchivePathName, OpenMode.Extract);
        }

        public void Open(OpenMode openMode)
        {
            if (ArchivePathName.Length == 0)
            {
                throw new IOException("Archive name has not been set.");
            }
            Open(ArchivePathName, openMode);
        }

        public void Open(string archivePathName, OpenMode openMode)
        {
            IntPtr zero = IntPtr.Zero;
            if (_archiveHandle != IntPtr.Zero)
            {
                Close();
            }
            ArchivePathName = archivePathName;
            var archiveData = new RAROpenArchiveDataEx();
            archiveData.Initialize();
            archiveData.ArcName = _archivePathName + "\0";
            archiveData.ArcNameW = _archivePathName + "\0";
            archiveData.OpenMode = (uint)openMode;
            if (_retrieveComment)
            {
                archiveData.CmtBuf = new string('\0', 0x10000);
                archiveData.CmtBufSize = 0x10000;
            }
            else
            {
                archiveData.CmtBuf = null;
                archiveData.CmtBufSize = 0;
            }
            zero = RAROpenArchiveEx(ref archiveData);
            switch (archiveData.OpenResult)
            {
                case 11:
                    throw new OutOfMemoryException("Insufficient memory to perform operation.");

                case 12:
                    throw new IOException("Archive _header broken");

                case 13:
                    throw new IOException("File is not a valid archive.");

                case 15:
                    throw new IOException("File could not be opened.");
            }
            _archiveHandle = zero;
            RARSetCallback(_archiveHandle, _callback, GetHashCode());
            if (archiveData.CmtState == 1)
            {
                _comment = archiveData.CmtBuf;
            }
            if (_password.Length != 0)
            {
                RARSetPassword(_archiveHandle, _password);
            }
            OnNewVolume(_archivePathName);
        }

        public bool ReadHeader()
        {
            if (_archiveHandle == IntPtr.Zero)
            {
                throw new IOException("Archive is not open.");
            }
            _header = new RARHeaderDataEx();
            _header.Initialize();
            _currentFile = null;
            switch (RARReadHeaderEx(_archiveHandle, ref _header))
            {
                case 10:
                    return false;

                case 12:
                    throw new IOException("Archive data is corrupt.");
            }
            if (((_header.Flags & 1) != 0) && (_currentFile != null))
            {
                _currentFile.ContinuedFromPrevious = true;
                return true;
            }
            _currentFile = new RARFileInfo();
            _currentFile.FileName = _header.FileNameW;
            if ((_header.Flags & 2) != 0)
            {
                _currentFile.ContinuedOnNext = true;
            }
            if (_header.PackSizeHigh != 0)
            {
                _currentFile.PackedSize = (_header.PackSizeHigh * 0x100000000L) + _header.PackSize;
            }
            else
            {
                _currentFile.PackedSize = _header.PackSize;
            }
            if (_header.UnpSizeHigh != 0)
            {
                _currentFile.UnpackedSize = (_header.UnpSizeHigh * 0x100000000L) + _header.UnpSize;
            }
            else
            {
                _currentFile.UnpackedSize = _header.UnpSize;
            }
            _currentFile.HostOS = (int)_header.HostOS;
            _currentFile.FileCRC = _header.FileCRC;
            _currentFile.FileTime = FromMSDOSTime(_header.FileTime);
            _currentFile.VersionToUnpack = (int)_header.UnpVer;
            _currentFile.Method = (int)_header.Method;
            _currentFile.FileAttributes = (int)_header.FileAttr;
            _currentFile.BytesExtracted = 0L;
            if ((_header.Flags & 0xe0) == 0xe0)
            {
                _currentFile.IsDirectory = true;
            }
            return OnNewFile();
        }

        public void Skip()
        {
            int result = RARProcessFile(_archiveHandle, 0, string.Empty, string.Empty);
            if (result != 0)
            {
                ProcessFileError(result);
            }
        }

        public void Test()
        {
            int result = RARProcessFile(_archiveHandle, 1, string.Empty, string.Empty);
            if (result != 0)
            {
                ProcessFileError(result);
            }
        }

        public void Close()
        {
            if (_archiveHandle != IntPtr.Zero)
            {
                int result = RARCloseArchive(_archiveHandle);
                if (result != 0)
                {
                    ProcessFileError(result);
                }
                else
                {
                    _archiveHandle = IntPtr.Zero;
                }
            }
        }

        public void Extract()
        {
            Extract(_destinationPath, string.Empty);
        }

        public void Extract(string destinationName)
        {
            Extract(string.Empty, destinationName);
        }

        private void Extract(string destinationPath, string destinationName)
        {
            _destinationInfo = new DriveInfo(destinationPath);
            int result = RARProcessFile(_archiveHandle, 2, destinationPath, destinationName);
            if (result != 0)
            {
                ProcessFileError(result);
            }
        }

        public void ExtractToDirectory(string destinationPath)
        {
            Extract(destinationPath, string.Empty);
        }

        public string[] ListFiles()
        {
            var list = new ArrayList();
            while (ReadHeader())
            {
                if (!_currentFile.IsDirectory)
                {
                    list.Add(_currentFile.FileName);
                }
                Skip();
            }
            var array = new string[list.Count];
            list.CopyTo(array);
            return array;
        }

        ~Unrar()
        {
            if (_archiveHandle != IntPtr.Zero)
            {
                RARCloseArchive(_archiveHandle);
                _archiveHandle = IntPtr.Zero;
            }
        }

        protected virtual int OnDataAvailable(IntPtr p1, int p2)
        {
            int num = 1;
            if (DataAvailable != null)
            {
                var destination = new byte[p2];
                Marshal.Copy(p1, destination, 0, p2);
                var e = new DataAvailableEventArgs(destination);
                DataAvailable(this, e);
                if (!e.ContinueOperation)
                {
                    num = -1;
                }
            }
            if (num != -1)
            {
                while ((num != -1) && !_destinationInfo.CanAllocte(p2))
                {
                    var args2 = new OutOfDiskSpaceEventArgs(_currentFile, _destinationInfo);
                    if (OutOfDiskSpace != null)
                    {
                        OutOfDiskSpace(this, args2);
                    }
                    if (!args2.ContinueOperation)
                    {
                        num = -1;
                    }
                    else
                    {
                        _destinationInfo = new DriveInfo(DestinationPath);
                    }
                }
                if (num != -1)
                {
                    _destinationInfo.Allocate(p2);
                    if (_currentFile != null)
                    {
                        _currentFile.BytesExtracted += p2;
                    }
                    if (((num != -1) && (ExtractionProgress != null)) && (_currentFile != null))
                    {
                        var progressEventArgs = new ExtractionProgressEventArgs();
                        progressEventArgs.DataLength = p2;
                        progressEventArgs.FileName = _currentFile.FileName;
                        progressEventArgs.FileSize = _currentFile.UnpackedSize;
                        progressEventArgs.BytesExtracted = _currentFile.BytesExtracted;
                        progressEventArgs.PercentComplete = _currentFile.PercentComplete;
                        ExtractionProgress(this, progressEventArgs);
                        if (!progressEventArgs.ContinueOperation)
                        {
                            num = -1;
                        }
                    }
                }
                return num;
            }
            return num;
        }

        protected virtual string OnMissingVolume(string volume)
        {
            string volumeName = string.Empty;
            if (MissingVolume != null)
            {
                var e = new MissingVolumeEventArgs(volume);
                MissingVolume(this, e);
                if (e.ContinueOperation)
                {
                    volumeName = e.VolumeName;
                }
            }
            return volumeName;
        }

        protected virtual bool OnNewFile()
        {
            if (NewFile != null)
            {
                var e = new NewFileEventArgs(_currentFile);
                NewFile(this, e);
                return e.ContinueOperation;
            }
            return true;
        }

        protected virtual int OnNewVolume(string volume)
        {
            int num = 1;
            if (NewVolume != null)
            {
                var e = new NewVolumeEventArgs(volume);
                NewVolume(this, e);
                if (!e.ContinueOperation)
                {
                    num = -1;
                }
            }
            return num;
        }

        protected virtual int OnPasswordRequired(IntPtr p1, int p2)
        {
            int num = -1;
            if (PasswordRequired == null)
            {
                throw new IOException("Password is required for extraction.");
            }
            var e = new PasswordRequiredEventArgs();
            PasswordRequired(this, e);
            if (!e.ContinueOperation || (e.Password.Length <= 0))
            {
                return num;
            }
            for (int i = 0; (i < e.Password.Length) && (i < p2); i++)
            {
                Marshal.WriteByte(p1, i, (byte)e.Password[i]);
            }
            Marshal.WriteByte(p1, e.Password.Length, 0);
            return 1;
        }

        #region Private Methods

        private DateTime FromMSDOSTime(uint dosTime)
        {
            int day = 0;
            int month = 0;
            int year = 0;
            int hour = 0;
            int minute = 0;
            var num7 = (ushort)((dosTime & -65536) >> 0x10);
            var num8 = (ushort)(dosTime & 0xffff);
            year = ((num7 & 0xfe00) >> 9) + 0x7bc;
            month = (num7 & 480) >> 5;
            day = num7 & 0x1f;
            hour = (num8 & 0xf800) >> 11;
            minute = (num8 & 0x7e) >> 5;
            return new DateTime(year, month, day, hour, minute, (num8 & 0x1f) << 1);
        }

        private void ProcessFileError(int result)
        {
            switch (((RarError)result))
            {
                case RarError.BadData:
                    throw new IOException("File CRC Error");

                case RarError.BadArchive:
                    throw new IOException("File is not a valid archive.");

                case RarError.UnknownFormat:
                    throw new OutOfMemoryException("Unknown archive format.");

                case RarError.OpenError:
                    throw new IOException("File could not be opened.");

                case RarError.CreateError:
                    throw new IOException("File could not be created.");

                case RarError.CloseError:
                    throw new IOException("File close error.");

                case RarError.ReadError:
                    throw new IOException("File read error.");

                case RarError.WriteError:
                    throw new IOException("File write error.");
            }
        }

        private int RARCallback(uint msg, int userData, IntPtr p1, int p2)
        {
            string volume;
            string str2;
            int num = -1;
            switch (((CallbackMessages)msg))
            {
                case CallbackMessages.VolumeChange:
                    volume = Marshal.PtrToStringAnsi(p1);
                    if (p2 != 1)
                    {
                        if (p2 != 0)
                        {
                            return num;
                        }
                        str2 = OnMissingVolume(volume);
                        if (str2.Length == 0)
                        {
                            return -1;
                        }
                        if (str2 != volume)
                        {
                            for (int i = 0; i < str2.Length; i++)
                            {
                                Marshal.WriteByte(p1, i, (byte)str2[i]);
                            }
                            Marshal.WriteByte(p1, str2.Length, 0);
                        }
                        return 1;
                    }
                    return OnNewVolume(volume);

                case CallbackMessages.ProcessData:
                    return OnDataAvailable(p1, p2);

                case CallbackMessages.NeedPassword:
                    return OnPasswordRequired(p1, p2);
            }
            return num;
        }

        #endregion

        #region Nested type: ArchiveFlags

        [Flags]
        private enum ArchiveFlags : uint
        {
            AuthenticityPresent = 0x20,
            CommentPresent = 2,
            EncryptedHeaders = 0x80,
            FirstVolume = 0x100,
            Lock = 4,
            NewNamingScheme = 0x10,
            RecoveryRecordPresent = 0x40,
            SolidArchive = 8,
            Volume = 1
        }

        #endregion

        #region Nested type: CallbackMessages

        private enum CallbackMessages : uint
        {
            NeedPassword = 2,
            ProcessData = 1,
            VolumeChange = 0
        }

        #endregion

        #region Nested type: Operation

        private enum Operation : uint
        {
            Extract = 2,
            Skip = 0,
            Test = 1
        }

        #endregion

        #region Nested type: RarError

        private enum RarError : uint
        {
            BadArchive = 13,
            BadData = 12,
            BufferTooSmall = 20,
            CloseError = 0x11,
            CreateError = 0x10,
            EndOfArchive = 10,
            InsufficientMemory = 11,
            OpenError = 15,
            ReadError = 0x12,
            UnknownError = 0x15,
            UnknownFormat = 14,
            WriteError = 0x13
        }

        #endregion

        #region Nested type: RARHeaderData

        [StructLayout(LayoutKind.Sequential)]
        private struct RARHeaderData
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string ArcName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string FileName;
            public uint Flags;
            public uint PackSize;
            public uint UnpSize;
            public uint HostOS;
            public uint FileCRC;
            public uint FileTime;
            public uint UnpVer;
            public uint Method;
            public uint FileAttr;
            [MarshalAs(UnmanagedType.LPStr)]
            public string CmtBuf;
            public uint CmtBufSize;
            public uint CmtSize;
            public uint CmtState;

            public void Initialize()
            {
                CmtBuf = new string('\0', 0x10000);
                CmtBufSize = 0x10000;
            }
        }

        #endregion

        #region Nested type: RARHeaderDataEx

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct RARHeaderDataEx
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x200)]
            public string ArcName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x400)]
            public string ArcNameW;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x200)]
            public string FileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x400)]
            public string FileNameW;
            public uint Flags;
            public uint PackSize;
            public uint PackSizeHigh;
            public uint UnpSize;
            public uint UnpSizeHigh;
            public uint HostOS;
            public uint FileCRC;
            public uint FileTime;
            public uint UnpVer;
            public uint Method;
            public uint FileAttr;
            [MarshalAs(UnmanagedType.LPStr)]
            public string CmtBuf;
            public uint CmtBufSize;
            public uint CmtSize;
            public uint CmtState;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
            public uint[] Reserved;

            public void Initialize()
            {
                CmtBuf = new string('\0', 0x10000);
                CmtBufSize = 0x10000;
            }
        }

        #endregion

        #region Nested type: RAROpenArchiveData

        [StructLayout(LayoutKind.Sequential)]
        public struct RAROpenArchiveData
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string ArcName;
            public uint OpenMode;
            public uint OpenResult;
            [MarshalAs(UnmanagedType.LPStr)]
            public string CmtBuf;
            public uint CmtBufSize;
            public uint CmtSize;
            public uint CmtState;

            public void Initialize()
            {
                CmtBuf = new string('\0', 0x10000);
                CmtBufSize = 0x10000;
            }
        }

        #endregion

        #region Nested type: RAROpenArchiveDataEx

        [StructLayout(LayoutKind.Sequential)]
        public struct RAROpenArchiveDataEx
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string ArcName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string ArcNameW;
            public uint OpenMode;
            public uint OpenResult;
            [MarshalAs(UnmanagedType.LPStr)]
            public string CmtBuf;
            public uint CmtBufSize;
            public uint CmtSize;
            public uint CmtState;
            public uint Flags;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
            public uint[] Reserved;

            public void Initialize()
            {
                CmtBuf = new string('\0', 0x10000);
                CmtBufSize = 0x10000;
                Reserved = new uint[0x20];
            }
        }

        #endregion

        #region Nested type: UNRARCallback

        private delegate int UNRARCallback(uint msg, int UserData, IntPtr p1, int p2);

        #endregion

        #region Nested type: VolumeMessage

        private enum VolumeMessage : uint
        {
            Ask = 0,
            Notify = 1
        }

        #endregion
    }
}